#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QImageReader>
#include <QPixmap>
#include <math.h>
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void ImageThreshold(uint8_t *pDat, uint cnt, uint8_t thre) {
    while (cnt--) {
        pDat[0] = pDat[0] >= thre ? 255 : 0;
        pDat++;
    }
}

void ImageThreshold(QImage &grayImg, uint8_t thre) {
    uint8_t *pDat = grayImg.bits();
    QSize imgSz = grayImg.size();
    uint32_t cnt = imgSz.width() * imgSz.height();
    ImageThreshold(pDat, cnt, thre);
}

typedef struct tagLineBlack{
    uint16_t x0; // first black dot in a horizontal line
    uint16_t x1; // last black dot in a horizontal line
    uint16_t y; // y of this horizontal line
    uint16_t blackCnt; // black dot count
}LineBlack_t;

typedef struct tagLineBlackStat{
    // bounding box of the black dot area
    uint16_t x0;
    uint16_t y0;
    uint16_t x1;
    uint16_t y1;
    uint16_t w;
    uint16_t h;
}LineBlackStat_t;

// 返回有多少行有黑点。
int GetBlackInfo(uint8_t *pImgIn, uint32_t w, uint32_t h,
    uint32_t y0 /*从哪行开始查起*/, LineBlack_t *pInfo0, uint32_t infoCnt, LineBlackStat_t *pStat)
{
    uint8_t *pImg;
    uint32_t *pImg32;
    uint x0, x1, y;
    uint blackedLineCnt = 0;
    uint conseqBlankCnt = 0;
    LineBlackStat_t stat = {0xFFFF,0xFFFF, 0, 0, 0, 0};
    if (w & 3)
        return -1;
    if ((uint32_t)pImgIn & 3)
        return -1;
    for (y=y0; y<h; y++) {
        // 从前往后搜索黑点
        pImg = pImgIn + w * y;
        pImg32 = (uint32_t*)pImg;
        for (x0=0; x0<w; x0+=4) {
            if (*pImg32++ == 0xFFFFFFFF)
                continue;
            // 找到了第一个黑点
            pImg32--;
            pImg = (uint8_t*)pImg32;
            // 检查是4个字节中哪个字节最先为0，小端模式
            for (int k=0; k<4; k++, pImg++) {
                if (*pImg == 0) {
                    x0 += k;
                    break;
                }
            }
            pInfo0[blackedLineCnt].x0 = x0;
            break;
        }
        if (x0 == w) {
            // 这一行没有黑点
            conseqBlankCnt++;
            continue;
        }
        // 遇到过多空白行，清零已经记录的黑行数从头算
        if (conseqBlankCnt >= 2)
            blackedLineCnt = 0;
        conseqBlankCnt = 0;
        // 从后往前搜索黑点
        pImg = (uint8_t*) pImgIn + w * (y + 1);
        pImg32 = (uint32_t*) pImg;
        for (x1=w-4; x1!=0; x1-= 4) {
            if (*(--pImg32) == 0xFFFFFFFF)
                continue;
            break;
        }
        // 检查是4个字节中哪个字节最先为0，小端模式
        pImg = (uint8_t*)pImg32 + 3;
        for (int k=0; k<4; k++, pImg--) {
            if (*pImg == 0) {
                x1 += 3 - k;
                break;
            }
        }
        if (x1 < x0) {
            // 这一行不能当作黑点行
            conseqBlankCnt++;
        } else {
            // 找到了最后一个黑点
            pInfo0[blackedLineCnt].x1 = x1;
            pInfo0[blackedLineCnt].y = y;
            pInfo0[blackedLineCnt].blackCnt = x1 - x0;
            if (++blackedLineCnt >= infoCnt)
                break;
        }

    }
    stat.y0 = pInfo0[0].y;
    stat.y1 = pInfo0[blackedLineCnt-1].y;
    for (uint i=0; i < blackedLineCnt; i++) {
        if (stat.x0 > pInfo0[i].x0)
            stat.x0 = pInfo0[i].x0;
        if (stat.x1 < pInfo0[i].x1)
            stat.x1 = pInfo0[i].x1;
        pImg = pImgIn + pInfo0[i].y * w;
        for (uint x=0;x<pInfo0[i].x0; x++)
            pImg[x] = 160;
        for (uint x=pInfo0[i].x1 + 1;x<w; x++)
            pImg[x] = 160;
    }
    stat.w = stat.x1 - stat.x0 + 1;
    stat.h = stat.y1 - stat.y0 + 1;
    if (pStat) *pStat = stat;
    return (int)blackedLineCnt;
}

// 返回倾斜的估计量
float CalcSlant(const LineBlack_t *pInfo, uint32_t infoCnt, LineBlackStat_t *pStat) {
    uint i, wAcc, wAvg, wBBox;
    float tanVal = 0;
    if (infoCnt == 0)
        return -180;
    // 计算平均黑线区域比例, 以及x坐标最小和最大处的pInfo数组下标（对应y坐标）
    uint xMin = 0x7FFF, xMax = 0, xMinAt = 0, xMaxAt = 0;
    for (i=0,wAcc=0 ; i< infoCnt; i++) {
        wAcc += pInfo[i].x1 - pInfo[i].x0 + 1;
        if (pInfo[i].x0 < xMin) {
            xMin = pInfo[i].x0;
            xMinAt = i;
        }
        if (pInfo[i].x1 > xMax) {
            xMax = pInfo[i].x1;
            xMaxAt = i;
        }
    }
    uint xMaxY = xMaxAt + pInfo[0].y;
    uint xMinY = xMinAt + pInfo[0].y;
    wAvg = wAcc / infoCnt;
    wBBox = pStat->w;
    uint wRateX1000 = wAvg * 1000 / wBBox;
    int slantSign = -1; // -1表示从左上到右下倾斜
    if (wRateX1000 < 750) {
        // 有倾斜
        if (100 * xMinAt / infoCnt  < 60) {
            // 倾斜角度很小
            int xTmp = 0;
            for (i=0; i<xMinAt; i++) {
                xTmp += (int)pInfo[i].x0 - (int)pInfo[i+1].x0;
            }
            if (xTmp * 2 > pStat->w)
                slantSign = 1;
        } else {
            slantSign = 1;
        }
        if (slantSign > 0) {
            // 从右上到左下倾斜，右侧定位符的实心黑方块右上角应作为右定位点
            xMaxAt = 0;
            xMaxY = pInfo[0].y;
            xMax = pInfo[0].x1;
            tanVal = (float)(xMinY - xMaxY) / (xMax - xMin) * 0.95f;
        } else {
            tanVal = -(float)(xMaxY - pInfo[0].y) / (xMax - pInfo[1].x0);
        }
    }
    float slopeAng = atanf(tanVal);
    return slopeAng;
}

void ImageTest(QImage &grayImg) {
    uint8_t *pDat = grayImg.bits();
    QSize imgSz = grayImg.size();
    uint32_t cnt = imgSz.width() * imgSz.height();
    while (cnt--) {
        pDat[0] = pDat[0] + 10 > 255 ? 255 : pDat[0] + 10;
        pDat++;
    }
}

typedef union tagMat2D3x3 {
    int32_t _2d[3][3];
    struct {
        int32_t a11, a12, a13, a21, a22, a23, a31, a32, a33;
    };
}Mat2D3x3_t;

void BuildRotateMat3x3(Mat2D3x3_t *pMat, float ang) {
    float cosVal = cosf(ang);
    float sinVal = sinf(ang);
    int32_t cosX1024 = (int)(cosVal * 1024.0f + 0.5f);
    int32_t sinX1024 = (int)(sinVal * 1024.0f + 0.5f);
    memset(pMat, 0, sizeof(*pMat));
    pMat->a11 = pMat->a22 = cosX1024;
    pMat->a12 = -sinX1024;
    pMat->a21 = sinX1024;
    pMat->a33 = 1;
}

void Mat2DRotate(const Mat2D3x3_t *pMat, const int vecIn[2], int vecOut[2]) {
    vecOut[0] = (vecIn[0] * pMat->a11 + vecIn[1] * pMat->a12 + 512) >> 10;
    vecOut[1] = (vecIn[0] * pMat->a21 + vecIn[1] * pMat->a22 + 512) >> 10;
}

typedef struct _tagGrayImg
{
    int w, h; //,cx, cy ;
    // int x0, y0, x1, y1; // (optional) bounding box
    uint8_t *pPix;
}GrayImg_t;

void RotateGrayFromCenter(const GrayImg_t *pSrc, GrayImg_t *pDst, LineBlackStat_t *pStat, float rotAng) {
    int wDstSafe = (float) pSrc->w / cosf(rotAng) + 1;
    int hDstSafe = (float) pSrc->h / cosf(rotAng) + 1;

    int vec3Pt[3] = {0,0,1};
    int cxSrc = (pStat->x0 + pStat->x1) >> 1;
    int cySrc = (pStat->y0 + pStat->y1) >> 1;
    int cxDst = pDst->w >> 1;
    int cyDst = pDst->h >> 1;
    int xMin = 0x7FFF, yMin = 0x7FFF, xMax = -32768, yMax = -32768;
    int x, y;
    Mat2D3x3_t mat;
    memset(pDst->pPix, -1, pDst->w * pDst->h);
    BuildRotateMat3x3(&mat, rotAng);

    //计算旋转后的bounding box
    uint8_t *pPixSrc = pSrc->pPix;
    for (y = 0; y<pSrc->h; y++) {
        for (x=0; x<pSrc->w; x++) {
            if (*pPixSrc++ != 0)
                continue;
            vec3Pt[0] = x - cxSrc;
            vec3Pt[1] = (pSrc->h - 1 - y) - cySrc;
            Mat2DRotate(&mat, vec3Pt, vec3Pt);
            vec3Pt[0] += cxDst;
            vec3Pt[1] = pDst->h - 1 - (vec3Pt[1] + cyDst);
            // 更新bounding box
            if (xMin > vec3Pt[0])
                xMin = vec3Pt[0];
            if (yMin > vec3Pt[1])
                yMin = vec3Pt[1];
            if (xMax < vec3Pt[0])
                xMax = vec3Pt[0];
            if (yMax < vec3Pt[1])
                yMax = vec3Pt[1];
            if (vec3Pt[0] < pDst->w && vec3Pt[1] < pDst->h) {
                pDst->pPix[vec3Pt[1] * pDst->w + vec3Pt[0]] = 0;
            }
        }
    }

    int newW = (xMax - xMin + 3) & ~3;
    for (y=0; y<yMax -yMin; y++) {
        memcpy(pDst->pPix + y * newW,
               pDst->pPix + (y + yMin) * pDst->w + xMin,
               newW);
    }
    pDst->w = newW;
    pDst->h = yMax - yMin;

}

typedef union tagMorphMask3x3 {
    int8_t _2d[3][3];
    struct {
        int8_t lt, ct, rt, lc, cc, rc, lb, cb, rb;
    };
}MorphMask3x3_t;

void BuildMorphCross(MorphMask3x3_t *p) {
    memset(p, 0, sizeof(*p));
    p->ct = p->cb = p->lc = p->rc = p->cc = 1;
}

void BuildMorphHorizontal(MorphMask3x3_t *p) {
    memset(p, 0, sizeof(*p));
    p->lc = p->cc = p->rc = 1;
}

void BuildMorphVertical(MorphMask3x3_t *p) {
    memset(p, 0, sizeof(*p));
    p->ct = p->cc = p->cb = 1;
}


#define _DO_DOT(r, c) do {\
    if (pMask->_2d[r][c]) { \
        if (*pSrc > max) { \
            max = *pSrc; \
        } else if (*pSrc < min) { \
            min = *pSrc; \
        }\
    }\
    pSrc++; \
}while(0)

void MorphGrayPix(const MorphMask3x3_t *pMask, const uint8_t *pSrcIn, uint8_t *pDst, int w, bool isWantMin) {
    uint8_t min = 0xFF, max = 0;
    const uint8_t *pSrc;
    // 上一行
    pSrc = pSrcIn - w - 1;
    _DO_DOT(0,0);
    _DO_DOT(0,1);
    _DO_DOT(0,2);
    pSrc = pSrcIn - 1;
    _DO_DOT(1,0);
    _DO_DOT(1,1);
    _DO_DOT(1,2);
    pSrc = pSrcIn + w - 1;
    _DO_DOT(2,0);
    _DO_DOT(2,1);
    _DO_DOT(2,2);
    if (isWantMin) {
        pDst[0] = min;
    } else {
        pDst[0] = max;
    }
}

void MorphGraySingleLine(MorphMask3x3_t *pMask, const uint8_t *pSrc, uint8_t *pDst, int w, bool isWantMin)
{
    uint8_t min = 0xFF, max = 0;
    uint8_t bkup;
    // 最左一个像素
    bkup = pMask->lc;
    pMask->lc = 0;
    MorphGrayPix(pMask, pSrc++, pDst++, w, isWantMin);
    pMask->lc = bkup;
    // 中间的像素
    for (int x=1; x<w-1; x++) {
        MorphGrayPix(pMask, pSrc++, pDst++, w, isWantMin);
    }
    // 最右一个像素
    bkup = pMask->rc;
    pMask->rc = 0;
    MorphGrayPix(pMask, pSrc++, pDst++, w, isWantMin);
    pMask->rc = bkup;
}
void MorphGray(const MorphMask3x3_t *pMask, const GrayImg_t *pSrc, GrayImg_t *pDst, bool isInflateBlack) {
    int x, y;
    const uint8_t *pInPix = pSrc->pPix;
    uint8_t *pOutPix = pDst->pPix;
    MorphMask3x3_t mask;
    // first line
    mask = pMask[0];
    mask.lt = mask.ct = mask.rt = 0;
    MorphGraySingleLine(&mask, pInPix, pOutPix, pSrc->w, isInflateBlack);
    pInPix += pSrc->w , pOutPix += pSrc->w;
    // middle lines
    mask = pMask[0];
    for (int y=1; y<pSrc->h-1; y++) {
        MorphGraySingleLine(&mask, pInPix, pOutPix , pSrc->w, isInflateBlack);
        pInPix += pSrc->w , pOutPix += pSrc->w;
    }
    // bottom line
    mask = pMask[0];
    mask.lb = mask.cb = mask.rb = 0;
    MorphGraySingleLine(&mask, pInPix, pOutPix , pSrc->w, isInflateBlack);

}

typedef struct tagXYWH{
    uint16_t x, y, w, h;
}XYWH_t;

void DrawVLine(GrayImg_t *pImg, int x, int y, int h) {
    uint8_t *pCol;
    pCol = pImg->pPix + x + y * pImg->w;
    for (int i=0; y<pImg->h && i<h; y++, i++) {
        pCol[0] = 192;
        pCol += pImg->w;
    }
}

void DrawHLine(GrayImg_t *pImg, int x, int y, int w) {
    uint8_t *pCol;
    pCol = pImg->pPix + x + y * pImg->w;
    for (int i=0; x<pImg->w && i<w; x++, i++) {
        pCol[i] = 192;
    }
}

void DrawRectXYWH(GrayImg_t *pImg, XYWH_t *pXywh) {
    int x;
    x = pXywh->x - 1;
    if (x < 0) x = 0;
    DrawVLine(pImg, x, 0, pImg->h);
    x = pXywh->x + pXywh->w;
    if (x>= pImg->w) x = pImg->w - 1;
    DrawVLine(pImg, x, 0, pImg->h);
    x = pXywh->x;
    DrawHLine(pImg, x, 0, pXywh->w);
    DrawHLine(pImg, x, pImg->h-1, pXywh->w);
}

// assume the chars are aligned, horizontal corrected
// xAry holds the best split X coordinates. So n characters get n+1 xAry[] elements.
int SplitDigit(GrayImg_t *pImg, int maxBlankCols, XYWH_t charRects[], int aryLen,bool dbgIsMark) {
    int charNdx = 0;
    int x, y;
    int conseqBlankCnt = 0; // consecutive blank col counter
    uint8_t *pCol;
    uint isBlank;
    int xStart = 0;
    uint isInChar=0;
    for (x=0; x<pImg->w; x++) {
        pCol = pImg->pPix + x;
        isBlank = 1;
        for (y=0; y<pImg->h; y++, pCol += pImg->w) {
            if (pCol[0] == 0) {
                isBlank = 0;
                break;
            } else {
            }
        }
        if (isBlank && ++conseqBlankCnt == maxBlankCols) {
            // a char is ended
            charRects[charNdx].x = xStart;
            charRects[charNdx].w = x - xStart - conseqBlankCnt;
            charRects[charNdx].y = 0;
            charRects[charNdx].h = pImg->h;
            isInChar = 0;
            if (++charNdx == aryLen)
                goto PostLoop;
        }
        if (!isBlank) {
            conseqBlankCnt = 0;
            if (!isInChar) {
                isInChar = 1;
                xStart = x;
                if (xStart < 0) xStart = 0;
            }
        }
    }
    if (isInChar) {
        charRects[charNdx].x = xStart;
        charRects[charNdx].w = pImg->w - xStart;
        charRects[charNdx].y = 0;
        charRects[charNdx].h = pImg->h;
        isInChar = 0;
        charNdx++;
    }
PostLoop:
    if (dbgIsMark) {
        for (int i=0; i<charNdx; i++) {
            DrawRectXYWH(pImg, charRects + i);
        }
    }
    return charNdx;
}

// return how many char have printed
int GrayImgCharPrint(const GrayImg_t *pImgChar, int cnt, GrayImg_t *pOut, int x, int y) {
    uint8_t *pPix1, *pPix2;
    int i, h;

    for (i=0; i<cnt; i++) {
        if (x + pImgChar->w >= pOut->w) {
            x = 0;
            y += pImgChar->h;
        }
        if (y + pImgChar->h >= pOut->h)
            return i;
        pPix2 = pOut->pPix + pOut->w * y + x;
        pPix1 = pImgChar->pPix;
        for (h=0; h<pImgChar->h; h++, pPix1 += pImgChar->w, pPix2 += pOut->w) {
            memcpy(pPix2, pPix1, pImgChar->w);
        }
        x += pImgChar->w;
    }
}

int GrayImgFillAlignCenter(const GrayImg_t *pIn, GrayImg_t *pOut) {
    int padL = (pOut->w - pIn->w) / 2;
    int padR = pOut->w - pIn->w - padL;
    int padT = (pOut->h - pIn->h) / 2;
    int padB = pOut->h - pIn->h - padB;
    const uint8_t *pPix1;
    uint8_t *pPix2;
    if (padL < 0 || padR < 0 || padT < 0 || padB < 0)
        return -1;
    pPix2 = pOut->pPix;
    for (int h=0; h<pOut->h; h++) {
        if (h < padT || h >= padB) {
            memset(pPix2, -1, pOut->w);
            pPix2 += pOut->w;
        }
        memset(pPix2, -1, padL);
        pPix2 += padL;
        memcpy(pPix2, pPix1, pIn->w);
        pPix2 += pIn->w;
        memset(pPix2, -1, padR);

    }
    return 0;
}

void BitBlt(GrayImg_t *pIn, GrayImg_t *pOut, int xSrc0, int ySrc0, int xDst0, int yDst0, int w, int h) {
    uint8_t *pPix1, *pPix2;
    pPix1 = pIn->pPix + pIn->w * ySrc0 + xSrc0;
    pPix2 = pOut->pPix + pOut->w * yDst0 + xDst0;
    while (h--) {
        memcpy(pPix2, pPix1, w);
        pPix1 += pIn->w;
        pPix2 += pOut->w;
    }
}

uint8_t g_imgBufs[2][480*320];
int dstNdx = 0;
#define MAX_CHAR 5
#define CNN_WIDTH 28
#define CNN_HEIGHT 28
uint8_t g_buf4CNN[CNN_HEIGHT * CNN_WIDTH * MAX_CHAR];
void MainWindow::on_btnGen_clicked()
{
    QImageReader reader("../code_02.png"); // 文件要放在工程目录下，是build-xxx的上级目录
    reader.setDecideFormatFromContent(true);
    QImage img = reader.read();
    QImage img2 = img.convertToFormat(QImage::Format_Grayscale8);
    QImage img3 = img2.copy();
    uint8_t *pSrc = img2.bits();
    uint w = img2.width();
    uint h = img2.height();
    ImageThreshold(img2, 128);
    bool isMorph = ui->chkMorph->isChecked();
    // >>> main part, MCU portable
    GrayImg_t srcImg = {.w=(int)w, .h=(int)h, .pPix = pSrc};
    GrayImg_t dstImg = {.w=480, .h=320, .pPix = g_imgBufs[dstNdx++ & 1]};
    LineBlack_t infoAry[200];
    LineBlackStat_t stat;

    uint32_t infoCnt = GetBlackInfo(pSrc, w, h, 0, infoAry, 200, &stat);
    float slopeAng = CalcSlant(infoAry, infoCnt, &stat);
    RotateGrayFromCenter(&srcImg, &dstImg, &stat, -slopeAng);

    // morphing
    if (isMorph) {
        MorphMask3x3_t mmask;
        srcImg = dstImg;
        dstImg.pPix = g_imgBufs[dstNdx++ & 1];
        BuildMorphCross(&mmask);
        MorphGray(&mmask, &srcImg, &dstImg, 0);
        // BuildMorphVertical(&mmask);
        MorphGray(&mmask, &srcImg, &dstImg, 1);
    }
    // split chars
    XYWH_t rects[8];
    int charCnt = SplitDigit(&dstImg, 2, rects, 8, false);
    // build same-width image buffer for CNN
    GrayImg_t img4CNN = {.w=CNN_WIDTH*MAX_CHAR, .h=CNN_HEIGHT, .pPix = g_buf4CNN};
    memset(g_buf4CNN, -1, sizeof(g_buf4CNN));
    int xSrc, xDst;
    for (int ndx = 1; ndx <charCnt - 1; ndx++) {
        xSrc = rects[ndx].x; // (rects[ndx].x + rects[ndx].w / 2) - 14;
        xDst = CNN_WIDTH * (ndx - 1);
        int charW = rects[ndx].w;
        int charH = dstImg.h;
        // 跳过前部空白的行
        int ySrc = 0;
        for (ySrc=0; ySrc<4; ySrc++) {
            uint8_t *pPix = dstImg.pPix + ySrc * dstImg.w + xSrc;

            for (int w=0; w<charW; w++) {
                if (*pPix++ == 0) {
                    ySrc = ySrc - 1 < 0 ? 0 : ySrc - 1;
                    goto DetectYTrail;
                }
            }
        }
DetectYTrail:
        // 跳过后部空白的行
        int yTrail = 0;
        for (yTrail=0; yTrail<4; yTrail++) {
            uint8_t *pPix = dstImg.pPix + (charH - 1 - yTrail) * dstImg.w + xSrc;
            for (int w=0; w<charW; w++) {
                if (*pPix++ == 0) {
                    yTrail = yTrail - 1 < 0 ? 0 : yTrail - 1;
                    // rects[ndx].h -= yTrail;
                    charH -= yTrail;
                    goto DoBitBlt;
                }
            }
        }
        charH -= yTrail;
DoBitBlt:

        int yDst = 0;
        int copyH = CNN_HEIGHT;
        if (charH - ySrc < CNN_HEIGHT) {
            yDst = (CNN_HEIGHT - charH + ySrc) >> 1;
            copyH = charH - ySrc;
        }
        int copyW = CNN_WIDTH;
        if (charW < CNN_WIDTH) {
            xDst += (CNN_WIDTH - charW) >> 1;
            copyW = charW;
        }
        BitBlt(&dstImg, &img4CNN, xSrc, ySrc, xDst, yDst, copyW, copyH);
    }
    dstImg = img4CNN;
     // <<<

    QImage imgOut = QImage(dstImg.pPix, dstImg.w, dstImg.h, dstImg.w, QImage::Format_Grayscale8);
    QString strRet = QString::number(slopeAng * 180.0f / 3.141592654f);
    ui->lblSlope->setText(strRet);
    QPixmap pixmap = QPixmap::fromImage(img2);
    QPixmap pixOut = QPixmap::fromImage(imgOut);
    // uint8_t *pData = img.data_ptr();
    ui->monitor->setPixmap(pixmap);
    ui->monOut->setPixmap(pixOut);
}


void MainWindow::on_chkMorph_stateChanged(int arg1)
{
    this->on_btnGen_clicked();
}

